package com.qen.common;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.qen.interfaces.MConsumer;
import com.qen.interfaces.SFunction;
import com.qen.wrapper.LambdaWrapper;
import com.qen.wrapper.support.LambdaMeta;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.beetl.sql.clazz.ClassDesc;
import org.beetl.sql.core.page.PageResult;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 填充
 *
 * @auther licz
 * @since 1.0.0
 */
@SuppressWarnings({"unchecked", "unused"})
public final class FillUtils {

    public static void fill(Object data, SFunction<?, ?> field, SFunction<?, ?> tagField) {
        fill(data, field, tagField, (MConsumer<LambdaWrapper<?>>) null);
    }

    public static void fill(Object data, SFunction<?, ?> field, SFunction<?, ?> tagField, MConsumer<LambdaWrapper<?>> consumer) {
        LambdaMeta meta = LambdaUtils.extract(tagField);
        Map<String, FieldCache> map = ClassDescHelper.getFieldMap(meta.getInstantiatedClass());
        FieldCache fieldCache = map.get(meta.getName());
        fill(data, field, getTagClass(fieldCache.getField()), tagField, consumer);
    }

    public static void fill(Object data, SFunction<?, ?> field, Class<?> oneClass, SFunction<?, ?> tagField) {
        fill(data, field, oneClass, tagField, null);
    }

    public static void fill(Object data, SFunction<?, ?> field, Class<?> oneClass, SFunction<?, ?> tagField, MConsumer<LambdaWrapper<?>> consumer) {
        ClassDesc classDesc = BeetlUtils.getSQLManager().getClassDesc(oneClass);
        Assert.notNull(classDesc.getIdAttr(), "not found key property by class %s", oneClass.getName());
        fill(data, field, new SF(oneClass, classDesc.getIdAttr()), tagField, consumer);
    }

    public static void fill(Object data, SFunction<?, ?> field, SFunction<?, ?> oneField, SFunction<?, ?> tagField) {
        fill(data, field, oneField, tagField, null);
    }

    public static void fill(Object data, SFunction<?, ?> field, SFunction<?, ?> oneField, SFunction<?, ?> tagField, MConsumer<LambdaWrapper<?>> consumer) {
        doFill(data, field, oneField, tagField, consumer);
    }

    private static <W> void doFill(Object data, SFunction<?, ?> field, SFunction<?, ?> oneField, SFunction<?, ?> tagField, MConsumer<LambdaWrapper<?>> consumer) {
        if (data == null || field == null || oneField == null || tagField == null) {
            return;
        }
        if (data instanceof PageResult) {
            doFill(((PageResult<?>) data).getList(), field, oneField, tagField, consumer);
            return;
        }
        if (data instanceof Collection) {
            Collection<?> collection = (Collection<?>) data;
            if (collection.isEmpty() || collection.stream().allMatch(Objects::isNull)) {
                return;
            }
        }

        LambdaMeta sourceMeta = LambdaUtils.extract(field);
        FieldCache sourceCache = ClassDescHelper.getFieldMap(sourceMeta.getInstantiatedClass()).get(sourceMeta.getName());

        LambdaMeta tagMeta = LambdaUtils.extract(tagField);
        FieldCache tagCache = ClassDescHelper.getFieldMap(tagMeta.getInstantiatedClass()).get(tagMeta.getName());

        FieldCache oneCache;
        if (oneField instanceof SF) {
            SF sf = (SF) oneField;
            oneCache = ClassDescHelper.getFieldMap(sf.getClazz()).get(sf.getName());
        } else {
            LambdaMeta oneMeta = LambdaUtils.extract(oneField);
            oneCache = ClassDescHelper.getFieldMap(oneMeta.getInstantiatedClass()).get(oneMeta.getName());
        }

        Class<?> wrapperClass = (oneField instanceof SF) ? ((SF) oneField).clazz : LambdaUtils.extract(oneField).getInstantiatedClass();
        LambdaWrapper<W> wrapper = new LambdaWrapper<W>((Class<W>) wrapperClass) {
            @Override
            public <R> LambdaWrapper<W> in(SFunction<R, ?> column, Collection<?> coll) {
                if (column instanceof SF) {
                    SF sf = (SF) column;
                    ClassDesc desc = BeetlUtils.getSQLManager().getClassDesc(sf.getClazz());
                    return super.in(alias + StrUtil.DOT + desc.getIdAttr(), coll);
                }
                return super.in(column, coll);
            }

            @Override
            public <R> LambdaWrapper<W> eq(SFunction<R, ?> column, Object val) {
                if (column instanceof SF) {
                    SF sf = (SF) column;
                    ClassDesc desc = BeetlUtils.getSQLManager().getClassDesc(sf.getClazz());
                    return super.eq(alias + StrUtil.DOT + desc.getIdAttr(), val);
                }
                return super.eq(column, val);
            }
        };

        boolean many = Collection.class.isAssignableFrom(tagCache.getType());

        if (data instanceof Collection) {
            List<?> collection = ((Collection<?>) data).stream().filter(Objects::nonNull).collect(Collectors.toList());
            Set<Object> sourceSet = collection.stream().map(sourceCache::getFieldValue).collect(Collectors.toSet());
            if (!sourceSet.isEmpty()) {
                wrapper.in(oneField, sourceSet);
                if (consumer != null) {
                    consumer.accept(wrapper);
                }
                List<?> list = wrapper.list();
                Map<Object, ?> map;
                if (many)
                    map = list.stream().collect(Collectors.groupingBy(oneCache::getFieldValue));
                else
                    map = list.stream().collect(Collectors.toMap(oneCache::getFieldValue, i -> i));
                //匹配
                collection.forEach(item -> tagCache.setFieldValue(item, map.get(sourceCache.getFieldValue(item))));
            }
        } else {
            Object sourceVal = sourceCache.getFieldValue(data);
            wrapper.eq(oneField, sourceVal);
            if (consumer != null) {
                consumer.accept(wrapper);
            }
            Object x = many ? wrapper.list(tagCache.getType()) : wrapper.one(tagCache.getType());
            if (x != null) {
                tagCache.setFieldValue(data, x);
            }
        }
    }

    private static <X> Class<X> getTagClass(Field field) {
        if (Collection.class.isAssignableFrom(field.getType())) {
            return (Class<X>) ClassDescHelper.getGenericType(field);
        } else {
            return (Class<X>) field.getType();
        }
    }

    @Getter
    @AllArgsConstructor
    private static class SF implements SFunction<Object, Object> {
        private Class<?> clazz;
        private String name;


        @Override
        public Object apply(Object o) {
            throw new UnsupportedOperationException();
        }
    }
}
